package dcutils.rpg;

// Import Java JDK Classes
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;

// Import DCUtils Classes
import dcutils.Lambda;

/**
 * Creates tasks to be executed in the future.<br/>
 * Also creates tasks to be executed repeatedly.<br/>
<pre style='color:#d1d1d1;background:#000000;'>
<span style='color:#e66170; font-weight:bold; '>final</span> long now = System.currentTimeMillis()<span style='color:#d2cd86; '>;</span>
<span style='color:#e66170; font-weight:bold; '>final</span> long delay = 1000L<span style='color:#d2cd86; '>;</span>
<span style='color:#e66170; font-weight:bold; '>final</span> FutureTask future = FutureTask.createFutureTask(new Lambda&lt;Void, Long>() <span style='color:#b060b0; '>{</span>
   <span style='color:#d2cd86; '>@</span>Override
   <span style='color:#e66170; font-weight:bold; '>public</span> <span style='color:#bb7977; font-weight:bold; '>Void</span> apply<span style='color:#d2cd86; '>(</span><span style='color:#bb7977; font-weight:bold; '>Long</span> param<span style='color:#d2cd86; '>)</span> <span style='color:#e66170; font-weight:bold; '>throws</span> <span style='color:#bb7977; font-weight:bold; '>IllegalArgumentException</span> <span style='color:#b060b0; '>{</span>
      <span style='color:#bb7977; font-weight:bold; '>System</span><span style='color:#d2cd86; '>.</span>out<span style='color:#d2cd86; '>.</span>println<span style='color:#d2cd86; '>(</span><span style='color:#00c4c4; '>"Current Time: "</span> <span style='color:#d2cd86; '>+</span> param<span style='color:#d2cd86; '>)</span><span style='color:#b060b0; '>;</span>
      <span style='color:#e66170; font-weight:bold; '>return</span> <span style='color:#e66170; font-weight:bold; '>null</span><span style='color:#b060b0; '>;</span>
   <span style='color:#b060b0; '>}</span> <span style='color:#9999a9; '>// END apply</span>
<span style='color:#b060b0; '>}</span>, now, delay)<span style='color:#d2cd86; '>;</span>
</pre>
 * @author dca
 */
public final class FutureTask {
   /** Private.  Can only be created from a static method. */
   private FutureTask() {
      started(false);
      ended(false);
   } // END constructor

   /** The started status. */
   private boolean started;
   public void started(boolean started) {
      this.started = started;
   } // END started
   public boolean started() {
      return started;
   } // ENd started

   /** The ended status. */
   private boolean ended;
   public void ended(boolean ended) {
      this.ended = ended;
   } // END ended
   public boolean ended() {
      System.out.printf("");
      return ended;
   } // ENd ended

   /**
    * Creates a task to be executed after a delay of <code>millis</code> milliseconds.
    * @param task A user defined object which performs an execution on <code>param</code> after <code>millis</code> milliseconds elapses. 
    * @param param The object to perform some task on.
    * @param time The time delay to do the task.
    * @param unit The time unit (used with the time param) to determine the time delay.
    * @return FutureTask The future object.
    * @throws IllegalArgumentException Thrown if <code>task</code> is undefined.
    */
   public static <R, P> FutureTask createFutureTask(final Lambda<R, P> task, final P param, final long time, final TimeUnit unit) throws IllegalArgumentException {
      if(null == task) throw new IllegalArgumentException("Task is undefined.  Cannot create a future for an undefined task.");
      if(null == unit) throw new IllegalArgumentException("unit is undefined.  Cannot create a future for an undefined time unit.");
      if(0 > time) throw new IllegalArgumentException(String.format("time is negative.  Cannot schedule a future at a negative time. (%d)", time));

      final FutureTask future = new FutureTask();

      final Timer timer = new Timer();

      timer.schedule(new TimerTask() {
         @Override
         public void run() {
            future.started(true);
            task.apply(param);
            timer.cancel();
            future.ended(true);
         } // END run
      }, unit.toMillis(time));

      return future;
   } // END createFutureTask

   /**
    * Creates a task which executes <code>param</code> after <code>start</code> milliseconds elapses, and <code>frequency</code> elapses thereafter, until <code>end</code> is reached.
    * @param task A user defined object which performs an execution on <code>param</code> after <code>start</code> milliseconds elapses, and <code>frequency</code> elapses thereafter, until <code>end</code> is reached.
    * @param param The object to perform some task on.
    * @param start The time to start the repeating task.
    * @param end The time to end the task.
    * @param frequency The frequency to repeat the task, from start to end.
    * @param unit The time unit (used with the start, end, and frequency params) to determine time.
    * @return FutureTask The future object.
    * @throws IllegalArgumentException Thrown either if <code>task</code> is undefined or end is less than start.
    */
   public static <R, P> FutureTask createRepeatingTask(final Lambda<R, P> task, final P param, final long start, final long end, final long frequency, final TimeUnit unit) throws IllegalArgumentException {
      if(null == task) throw new IllegalArgumentException("Task is undefined.  Cannot create a future for an undefined task.");
      if(null == unit) throw new IllegalArgumentException("unit is undefined.  Cannot create a future for an undefined time unit.");
      if(end < start) throw new IllegalArgumentException(String.format("end is less than start. (%d < %d)", end, start));
      if(0 > start) throw new IllegalArgumentException(String.format("start is negative.  Cannot schedule a future to start at a negative time. (%d)", start));
      if(0 > end) throw new IllegalArgumentException(String.format("end is negative.  Cannot schedule a future to end at a negative time. (%d)", end));
      if(0 > frequency) throw new IllegalArgumentException(String.format("frequency is negative.  Cannot schedule a future to recur at a negative frequency. (%d)", frequency));

      final FutureTask future = new FutureTask();

      final Timer timer = new Timer();

      timer.schedule(new TimerTask() {
         private long curr = start;

         @Override
         public void run() {
            future.started(true);

            if(curr < end) {
               curr += frequency;
               task.apply(param);
            } else {
               timer.cancel();
               future.ended(true);
            } // END if/else
         } // END run
      }, unit.toMillis(start), unit.toMillis(frequency));

      return future;
   } // END createRepeatingTask

   /**
    * Creates a task which executes <code>param</code> after <code>start</code> milliseconds elapses, and <code>frequency</code> elapses thereafter, until <code>end</code> is reached or until the task&apos;s return matches <code>check</code>.
    * @param task A user defined object which performs an execution on <code>param</code> after <code>start</code> milliseconds elapses, and <code>frequency</code> elapses thereafter, until <code>end</code> is reached.
    * @param param The object to perform some task on.
    * @param check The object to check the result of the task.  If check matches the result, then the repetition is stopped.
    * @param start The time to start the repeating task.
    * @param end The time to end the task.
    * @param frequency The frequency to repeat the task, from start to end.
    * @param unit The time unit (used with the start, end, and frequency params) to determine time.
    * @return FutureTask The future object.
    * @throws IllegalArgumentException Thrown either if <code>task</code> is undefined or end is less than start.
    */
   public static <R, P> FutureTask createRepeatingTask(final Lambda<R, P> task, final P param, final R check, final long start, final long end, final long frequency, final TimeUnit unit) throws IllegalArgumentException {
      if(null == task) throw new IllegalArgumentException("Task is undefined.  Cannot create a future for an undefined task.");
      if(null == unit) throw new IllegalArgumentException("unit is undefined.  Cannot create a future for an undefined time unit.");
      if(end < start) throw new IllegalArgumentException(String.format("end is less than start. (%d < %d)", end, start));
      if(0 > start) throw new IllegalArgumentException(String.format("start is negative.  Cannot schedule a future to start at a negative time. (%d)", start));
      if(0 > end) throw new IllegalArgumentException(String.format("end is negative.  Cannot schedule a future to end at a negative time. (%d)", end));
      if(0 > frequency) throw new IllegalArgumentException(String.format("frequency is negative.  Cannot schedule a future to recur at a negative frequency. (%d)", frequency));

      final FutureTask future = new FutureTask();

      final Timer timer = new Timer();

      timer.schedule(new TimerTask() {
         private long curr = start;

         @Override
         public void run() {
            future.started(true);

            if(curr < end) {
               curr += frequency;

               if(task.apply(param) == check) {
                  timer.cancel();
                  future.ended(true);
               } // END if
            } else {
               timer.cancel();
               future.ended(true);
            } // END if/else
         } // END run
      }, unit.toMillis(start), unit.toMillis(frequency));

      return future;
   } // END createRepeatingTask

   /**
    * Creates a task which executes <code>param</code> after <code>start</code> milliseconds elapses, and <code>frequency</code> elapses thereafter, until the task&apos;s return matches <code>check</code>.
    * @param task A user defined object which performs an execution on <code>param</code> after <code>start</code> milliseconds elapses, and <code>frequency</code> elapses thereafter, until <code>end</code> is reached.
    * @param param The object to perform some task on.
    * @param check The object to check the result of the task.  If check matches the result, then the repetition is stopped.
    * @param start The time to start the repeating task.
    * @param frequency The frequency to repeat the task, from start to end.
    * @param unit The time unit (used with start and frequency params) to determine the time.
    * @return FutureTask The future object.
    * @throws IllegalArgumentException Thrown either if <code>task</code> is undefined or end is less than start.
    */
   public static <R, P> FutureTask createRepeatingTask(final Lambda<R, P> task, final P param, final R check, final long start, final long frequency, final TimeUnit unit) throws IllegalArgumentException {
      if(null == task) throw new IllegalArgumentException("Task is undefined.  Cannot create a future for an undefined task.");
      if(null == unit) throw new IllegalArgumentException("unit is undefined.  Cannot create a future for an undefined time unit.");
      if(0 > start) throw new IllegalArgumentException(String.format("start is negative.  Cannot schedule a future to start at a negative time. (%d)", start));
      if(0 > frequency) throw new IllegalArgumentException(String.format("frequency is negative.  Cannot schedule a future to recur at a negative frequency. (%d)", frequency));

      final FutureTask future = new FutureTask();

      final Timer timer = new Timer();

      timer.schedule(new TimerTask() {
         @Override
         public void run() {
            future.started(true);

            if(task.apply(param) == check) {
               timer.cancel();
               future.ended(true);
            } // END if
         } // END run
      }, unit.toMillis(start), unit.toMillis(frequency));

      return future;
   } // END createRepeatingTask
} // END class FutureTask
